fix update commands to preserve full PUT payloads#20
Conversation
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughFour update commands—credential, gateway-group, and proto—are refactored to track which CLI flags were explicitly provided, fetch current resource state, conditionally parse and merge only provided changes, and send full PUT payloads that preserve unmodified fields required by the API schema. ChangesPartial Update Flag Mode Fix
Sequence Diagram(s)sequenceDiagram
participant CLI as a7 CLI
participant API as Control Plane API
participant DB as Storage
CLI->>API: GET /resource/{id}
API->>DB: read resource
DB-->>API: current resource JSON
API-->>CLI: current resource JSON
CLI->>API: PUT /resource/{id} with merged payload
API->>DB: write updated resource
DB-->>API: stored resource
API-->>CLI: updated resource JSON
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (1 error, 1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR fixes flag-based update commands that previously sent partial PUT payloads to API endpoints that require full resource replacement, by first reading the current resource and then merging only the explicitly-provided flags before issuing the PUT.
Changes:
proto update,credential update, andgateway-group update(flag mode) now GET the current resource and preserve unspecified fields when building the PUT payload.- Introduces
*Setbooleans (based oncobra.Flags().Changed(...)) to distinguish “flag not provided” vs “provided empty value”. - Adds/updates unit tests to assert preservation of required/existing fields in the generated PUT payload.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| pkg/cmd/proto/update/update.go | Fetches current proto and merges only changed flag fields before PUT to preserve required/existing fields. |
| pkg/cmd/proto/update/update_test.go | Updates test to validate PUT payload preserves content unless --content is explicitly set. |
| pkg/cmd/gateway-group/update/update.go | Fetches current gateway group and merges only changed flag fields before PUT to preserve required name. |
| pkg/cmd/gateway-group/update/update_test.go | New test ensuring required fields (e.g., name) are preserved when updating description/labels. |
| pkg/cmd/credential/update/update.go | Fetches current credential and merges only changed flag fields before PUT to preserve name/plugins. |
| pkg/cmd/credential/update/update_test.go | Updates tests to validate PUT payload preserves existing name/plugins and adds required GET mock for error-path test. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| var request api.GatewayGroup | ||
| if err := json.Unmarshal(currentBody, &request); err != nil { | ||
| return fmt.Errorf("failed to decode current gateway group: %w", err) | ||
| } | ||
| request.ID = opts.ID |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
test/e2e/credential_test.go (2)
138-140: ⚡ Quick winRegister credential cleanup immediately after
actualIDis known.If an assertion fails before Line 162, the explicit delete won’t run and can leave residue in shared e2e environments.
Suggested patch
actualID, ok := created["id"].(string) require.True(t, ok && actualID != "", "credential create response should contain generated id: %v", created) + t.Cleanup(func() { + _, cleanupStderr, cleanupErr := runA7WithEnv(env, "credential", "delete", actualID, + "--consumer", username, "--force", "-g", gatewayGroup) + if cleanupErr != nil { + t.Logf("credential cleanup failed for %s: %s", actualID, cleanupStderr) + } + }) ... - stdout, stderr, err = runA7WithEnv(env, "credential", "delete", actualID, - "--consumer", username, "--force", "-g", gatewayGroup) - require.NoError(t, err, "stdout=%s stderr=%s", stdout, stderr)Also applies to: 162-164
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/e2e/credential_test.go` around lines 138 - 140, After extracting actualID from created (actualID, ok := created["id"].(string) and the require.True check), immediately register the cleanup that deletes the credential (use t.Cleanup or an immediate defer) so the delete runs even if subsequent assertions fail; move the existing explicit delete logic currently around the later block (the delete at lines ~162-164) into that immediate cleanup closure and reference actualID there to ensure removal in shared e2e environments.
152-153: ⚡ Quick winStrengthen preservation checks to validate nested plugin content.
Current assertions only verify that
"key-auth"exists. They won’t catch regressions where nested fields are lost/changed while the key remains present.Suggested patch
plugins := requireJSONObject(t, credential["plugins"], "credential.plugins") assert.Contains(t, plugins, "key-auth") + keyAuth := requireJSONObject(t, plugins["key-auth"], "credential.plugins.key-auth") + assert.Equal(t, "e2e-update-key-12345", keyAuth["key"]) ... plugins = requireJSONObject(t, credential["plugins"], "credential.plugins") assert.Contains(t, plugins, "key-auth") + keyAuth = requireJSONObject(t, plugins["key-auth"], "credential.plugins.key-auth") + assert.Equal(t, "e2e-update-key-12345", keyAuth["key"])Also applies to: 159-160
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/e2e/credential_test.go` around lines 152 - 153, The test currently only checks presence of "key-auth" in plugins (using plugins := requireJSONObject(t, credential["plugins"], "credential.plugins") and assert.Contains), which misses regressions where the nested plugin object changes; update the test to call requireJSONObject(t, plugins["key-auth"], "credential.plugins.key-auth") to assert the plugin value is an object and then assert its expected nested fields (e.g., required config/keys) exist and have expected types/values using the same require* helpers used elsewhere in the file; apply the same change for the other occurrence around the second assertion block.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@test/e2e/credential_test.go`:
- Line 134: The test currently passes raw stdout and stderr into require.NoError
(the call using require.NoError(t, err, "stdout=%s stderr=%s", stdout, stderr))
which can leak plugin payloads; update the checks to avoid printing raw command
output by removing stdout/stderr from the failure message (use
require.NoError(t, err) or a generic message) or redact sensitive fields before
logging (e.g., replace tokens/keys in the stdout/stderr strings), and apply this
change for every occurrence that uses stdout and stderr variables in
require.NoError in the credential_test.go tests.
---
Nitpick comments:
In `@test/e2e/credential_test.go`:
- Around line 138-140: After extracting actualID from created (actualID, ok :=
created["id"].(string) and the require.True check), immediately register the
cleanup that deletes the credential (use t.Cleanup or an immediate defer) so the
delete runs even if subsequent assertions fail; move the existing explicit
delete logic currently around the later block (the delete at lines ~162-164)
into that immediate cleanup closure and reference actualID there to ensure
removal in shared e2e environments.
- Around line 152-153: The test currently only checks presence of "key-auth" in
plugins (using plugins := requireJSONObject(t, credential["plugins"],
"credential.plugins") and assert.Contains), which misses regressions where the
nested plugin object changes; update the test to call requireJSONObject(t,
plugins["key-auth"], "credential.plugins.key-auth") to assert the plugin value
is an object and then assert its expected nested fields (e.g., required
config/keys) exist and have expected types/values using the same require*
helpers used elsewhere in the file; apply the same change for the other
occurrence around the second assertion block.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: cbe9759d-46e1-4181-9754-358090742b2e
📒 Files selected for processing (3)
test/e2e/credential_test.gotest/e2e/gateway_group_test.gotest/e2e/proto_test.go
| type mockConfig struct{} | ||
|
|
||
| func (m *mockConfig) BaseURL() string { return "" } | ||
| func (m *mockConfig) Token() string { return "" } | ||
| func (m *mockConfig) GatewayGroup() string { return "" } |
| if err := json.Unmarshal([]byte(opts.PluginsJSON), &pl); err != nil { | ||
| return fmt.Errorf("invalid --plugins-json: %w", err) |
| stdout, _, err := runA7WithEnv(env, "credential", "create", credID, | ||
| "--consumer", username, | ||
| "--plugins-json", `{"key-auth":{"key":"e2e-positional-key-12345"}}`, | ||
| "-g", gatewayGroup) | ||
| require.NoError(t, err, "stdout=%s stderr=%s", stdout, stderr) | ||
| require.NoError(t, err, "credential create failed") | ||
|
|
| _, _, cleanupErr := runA7WithEnv(env, "credential", "delete", actualID, | ||
| "--consumer", username, "--force", "-g", gatewayGroup) | ||
| if cleanupErr != nil { | ||
| t.Logf("credential cleanup failed for %s", actualID) |
| var bodyReq api.Credential | ||
| if err := json.Unmarshal(currentBody, &bodyReq); err != nil { | ||
| return fmt.Errorf("failed to decode current credential: %w", err) | ||
| } | ||
| bodyReq.ID = opts.ID | ||
| if opts.DescSet { | ||
| bodyReq.Desc = opts.Desc | ||
| } | ||
| if opts.PluginsSet { | ||
| bodyReq.Plugins = pl | ||
| } | ||
| if len(labels) > 0 { | ||
| if opts.LabelsSet { | ||
| bodyReq.Labels = labels | ||
| } |
| var bodyReq api.Proto | ||
| if err := json.Unmarshal(currentBody, &bodyReq); err != nil { | ||
| return fmt.Errorf("failed to decode current proto: %w", err) | ||
| } | ||
| bodyReq.ID = opts.ID | ||
| if opts.DescSet { | ||
| bodyReq.Desc = opts.Desc | ||
| } | ||
| if len(labels) > 0 { | ||
| if opts.ContentSet { | ||
| bodyReq.Content = opts.Content | ||
| } | ||
| if opts.LabelsSet { | ||
| bodyReq.Labels = labels | ||
| } |
Summary
Fixes #18.
This PR changes flag-based update commands for resources whose API endpoints require full PUT payloads:
a7 credential updatenow reads the existing credential and preserves fields such asnameandpluginsunless the corresponding flags are provided.a7 proto updatenow preserves existingcontentunless--contentis provided.a7 gateway-group updatenow preserves required fields such asnamewhen only description, prefix, or labels are updated.File-based update mode remains unchanged and continues to send the provided file payload directly.
Validation
go test ./pkg/cmd/credential/update ./pkg/cmd/proto/update ./pkg/cmd/gateway-group/updatego test ./...credential update --descpreserves existing plugins.proto update --descpreserves existing content.gateway-group update --descriptionpreserves existing name.Follow-up
API capability/version boundary findings from the same live validation are tracked separately in #19.
Summary by CodeRabbit
Bug Fixes
Tests